import { Editor, Range, Element } from 'slate';
import { EDITOR_ELEMENT_TYPE, PLUGINS_KEYS, Plugin } from '../types';
import CodeBlock_ from './CodeBlock';
import Prism from 'prismjs';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-java';
import 'prismjs/themes/prism.min.css';
import { isMatchKeyDown } from '@/core/utils/is';
import { KEYDOWNS } from '@/core/types';

const DEFAULT_LANGUAGE = 'javascript';

function codeTokenize(code: string, language: string) {
  const range = [];
  const tokens = Prism.tokenize(code, Prism.languages[language.toLowerCase()]);
  let start = 0;
  for (const token of tokens) {
    const length = token?.length;
    const end = start + length;

    if (typeof token !== 'string') {
      range.push({
        type: token.type,
        start,
        end,
      });
    }

    start = end;
  }
  return range;
}

function CodeBlock(editor: Editor): Plugin {
  return {
    key: PLUGINS_KEYS.CODEBLOCK,
    type: EDITOR_ELEMENT_TYPE.BLOCK,
    match: (props) => !!props.element[PLUGINS_KEYS.CODEBLOCK],
    render: (context) => {
      return (
        <CodeBlock_ editor={editor} element={context.props.element} {...context.props.element[PLUGINS_KEYS.CODEBLOCK]}>
          {context.children}
        </CodeBlock_>
      );
    },
    decorate: (entry) => {
      const range: Range[] = [];
      const [node, path] = entry;
      if (Element.isElement(node) && Editor.isBlock(editor, node) && node[PLUGINS_KEYS.CODEBLOCK]) {
        const str = Editor.string(editor, path);
        const language = node[PLUGINS_KEYS.CODEBLOCK].language || DEFAULT_LANGUAGE;

        const codeToken = codeTokenize(str, language);

        for (const ct of codeToken) {
          range.push({
            anchor: {
              path,
              offset: ct.start,
            },
            focus: {
              path,
              offset: ct.end,
            },
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            [PLUGINS_KEYS.CODEBLOCKITEM]: ct.type,
          });
        }
      }
      return range;
    },
    matchLeaf: (props) => !!props.leaf[PLUGINS_KEYS.CODEBLOCKITEM],
    renderLeaf: (context) => {
      context.classList.push('token', context.props.leaf[PLUGINS_KEYS.CODEBLOCKITEM] || '');

      return context.children;
    },
    onKeyDown: (event) => {
      // Ctrl + A 监听全选事件
      if (event.ctrlKey && isMatchKeyDown(event, KEYDOWNS.KEYA)) {
        event.preventDefault();
        const selection = window.getSelection();
        const range = document.createRange();
        if (selection?.rangeCount && selection?.rangeCount > 0) selection?.removeAllRanges();
        const code = document.getElementById('code-block');
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        range.selectNode(code!);
        selection?.addRange(range);
      }
    },
  };
}

export default CodeBlock;
